/**
 * Copyright © 2016-2022 The Thingsboard Authors
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.thingsboard.server.controller;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.IotConfig;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.IotConfigId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;

import java.util.List;

import static org.thingsboard.server.controller.ControllerConstants.*;

@RestController
@TbCoreComponent
@RequestMapping("/api")
public class IotConfigController extends BaseController {

    protected static final String IOTCONFIG_ID = "iotConfigId";
    protected static final String IOTCONFIG_ID_PARAM_DESCRIPTION = "A string value representing the iotConfig id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
    protected static final String TENANT_OR_IOTCONFIG_AUTHORITY_PARAGRAPH = "\n\nAvailable for users with 'TENANT_ADMIN' or 'IOTCONFIG_USER' authority.";
    protected static final String IOTCONFIG_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the iotConfig title.";
    protected static final String IOTCONFIG_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title";

    public static final String IS_PUBLIC = "isPublic";
    public static final String IOTCONFIG_SECURITY_CHECK = "If the user has the authority of 'Tenant Administrator', the server checks that the iotConfig is owned by the same tenant. " +
            "If the user has the authority of 'IotConfig User', the server checks that the user belongs to the iotConfig.";

    @ApiOperation(value = "Get IotConfig (getIotConfigById)",
            notes = "Get the IotConfig object based on the provided IotConfig Id. "
                    + IOTCONFIG_SECURITY_CHECK + TENANT_OR_IOTCONFIG_AUTHORITY_PARAGRAPH)
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'IOTCONFIG_USER')")
    @RequestMapping(value = "/iotConfig/{iotConfigId}", method = RequestMethod.GET)
    @ResponseBody
    public IotConfig getIotConfigById(
            @ApiParam(value = IOTCONFIG_ID_PARAM_DESCRIPTION)
            @PathVariable(IOTCONFIG_ID) String strIotConfigId) throws ThingsboardException {
        checkParameter(IOTCONFIG_ID, strIotConfigId);
        try {
            IotConfigId iotConfigId = new IotConfigId(toUUID(strIotConfigId));
            IotConfig iotConfig = checkIotConfigId(iotConfigId, Operation.READ);
            if (!iotConfig.getAdditionalInfo().isNull()) {
                processDashboardIdFromAdditionalInfo((ObjectNode) iotConfig.getAdditionalInfo(), HOME_DASHBOARD);
            }
            return iotConfig;
        } catch (Exception e) {
            throw handleException(e);
        }
    }


    @ApiOperation(value = "Get short IotConfig info (getShortIotConfigInfoById)",
            notes = "Get the short iotConfig object that contains only the title and 'isPublic' flag. "
                    + IOTCONFIG_SECURITY_CHECK + TENANT_OR_IOTCONFIG_AUTHORITY_PARAGRAPH)
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'IOTCONFIG_USER')")
    @RequestMapping(value = "/iotConfig/{iotConfigId}/shortInfo", method = RequestMethod.GET)
    @ResponseBody
    public JsonNode getShortIotConfigInfoById(
            @ApiParam(value = IOTCONFIG_ID_PARAM_DESCRIPTION)
            @PathVariable(IOTCONFIG_ID) String strIotConfigId) throws ThingsboardException {
        checkParameter(IOTCONFIG_ID, strIotConfigId);
        try {
            IotConfigId iotConfigId = new IotConfigId(toUUID(strIotConfigId));
            IotConfig iotConfig = checkIotConfigId(iotConfigId, Operation.READ);
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode infoObject = objectMapper.createObjectNode();
            infoObject.put("title", iotConfig.getTitle());
            // infoObject.put(IS_PUBLIC, iotConfig.isPublic());
            return infoObject;
        } catch (Exception e) {
            throw handleException(e);
        }
    }

    @ApiOperation(value = "Get IotConfig Title (getIotConfigTitleById)",
            notes = "Get the title of the iotConfig. "
                    + IOTCONFIG_SECURITY_CHECK + TENANT_OR_IOTCONFIG_AUTHORITY_PARAGRAPH)
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'IOTCONFIG_USER')")
    @RequestMapping(value = "/iotConfig/{iotConfigId}/title", method = RequestMethod.GET, produces = "application/text")
    @ResponseBody
    public String getIotConfigTitleById(
            @ApiParam(value = IOTCONFIG_ID_PARAM_DESCRIPTION)
            @PathVariable(IOTCONFIG_ID) String strIotConfigId) throws ThingsboardException {
        checkParameter(IOTCONFIG_ID, strIotConfigId);
        try {
            IotConfigId iotConfigId = new IotConfigId(toUUID(strIotConfigId));
            IotConfig iotConfig = checkIotConfigId(iotConfigId, Operation.READ);
            return iotConfig.getTitle();
        } catch (Exception e) {
            throw handleException(e);
        }
    }

    @ApiOperation(value = "Create or update IotConfig (saveIotConfig)",
            notes = "Creates or Updates the IotConfig. When creating iotConfig, platform generates IotConfig Id as " + UUID_WIKI_LINK +
                    "The newly created IotConfig Id will be present in the response. " +
                    "Specify existing IotConfig Id to update the IotConfig. " +
                    "Referencing non-existing IotConfig Id will cause 'Not Found' error." + TENANT_AUTHORITY_PARAGRAPH)
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
    @RequestMapping(value = "/iotConfig", method = RequestMethod.POST)
    @ResponseBody
    public IotConfig saveIotConfig(@ApiParam(value = "A JSON value representing the iotConfig.") @RequestBody IotConfig iotConfig) throws ThingsboardException {
        try {
            iotConfig.setTenantId(getCurrentUser().getTenantId());

            checkEntity(iotConfig.getId(), iotConfig, Resource.IOT_CONFIG);

            IotConfig savedIotConfig = checkNotNull(iotConfigService.saveIotConfig(iotConfig));

            // logEntityAction(savedIotConfig.getId(), savedIotConfig, savedIotConfig.getId(), iotConfig.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);

            if (iotConfig.getId() != null) {
                sendEntityNotificationMsg(savedIotConfig.getTenantId(), savedIotConfig.getId(), EdgeEventActionType.UPDATED);
            }

            return savedIotConfig;
        } catch (Exception e) {

            logEntityAction(new IotConfigId(ModelConstants.NULL_UUID), iotConfig,
                    null, iotConfig.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);

            throw handleException(e);
        }
    }

    @ApiOperation(value = "Delete IotConfig (deleteIotConfig)",
            notes = "Deletes the IotConfig and all iotConfig Users. " +
                    "All assigned Dashboards, Assets, Devices, etc. will be unassigned but not deleted. " +
                    "Referencing non-existing IotConfig Id will cause an error." + TENANT_AUTHORITY_PARAGRAPH)
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
    @RequestMapping(value = "/iotConfig/{iotConfigId}", method = RequestMethod.DELETE)
    @ResponseStatus(value = HttpStatus.OK)
    public void deleteIotConfig(@ApiParam(value = IOTCONFIG_ID_PARAM_DESCRIPTION)
                                @PathVariable(IOTCONFIG_ID) String strIotConfigId) throws ThingsboardException {
        checkParameter(IOTCONFIG_ID, strIotConfigId);
        try {
            IotConfigId iotConfigId = new IotConfigId(toUUID(strIotConfigId));
            IotConfig iotConfig = checkIotConfigId(iotConfigId, Operation.DELETE);

            List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(getTenantId(), iotConfigId);

            iotConfigService.deleteIotConfig(getTenantId(), iotConfigId);

            // logEntityAction(iotConfigId, iotConfig, iotConfig.getId(), ActionType.DELETED, null, strIotConfigId);

            sendDeleteNotificationMsg(getTenantId(), iotConfigId, relatedEdgeIds);
            tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), iotConfigId, ComponentLifecycleEvent.DELETED);
        } catch (Exception e) {

            logEntityAction(emptyId(EntityType.IOT_CONFIG),
                    null,
                    null,
                    ActionType.DELETED, e, strIotConfigId);

            throw handleException(e);
        }
    }

    @ApiOperation(value = "Get Tenant IotConfigs (getIotConfigs)",
            notes = "Returns a page of iotConfigs owned by tenant. " +
                    PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
    @RequestMapping(value = "/iotConfigs", params = {"pageSize", "page"}, method = RequestMethod.GET)
    @ResponseBody
    public PageData<IotConfig> getIotConfigs(
            @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
            @RequestParam int pageSize,
            @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
            @RequestParam int page,
            @ApiParam(value = IOTCONFIG_TEXT_SEARCH_DESCRIPTION)
            @RequestParam(required = false) String textSearch,
            @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = IOTCONFIG_SORT_PROPERTY_ALLOWABLE_VALUES)
            @RequestParam(required = false) String sortProperty,
            @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
            @RequestParam(required = false) String sortOrder) throws ThingsboardException {
        try {
            PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
            TenantId tenantId = getCurrentUser().getTenantId();
            return checkNotNull(iotConfigService.findIotConfigsByTenantId(tenantId, pageLink));
        } catch (Exception e) {
            throw handleException(e);
        }
    }

    @ApiOperation(value = "Get Tenant IotConfig by IotConfig title (getTenantIotConfig)",
            notes = "Get the IotConfig using IotConfig Title. " + TENANT_AUTHORITY_PARAGRAPH)
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
    @RequestMapping(value = "/tenant/iotConfigs", params = {"iotConfigTitle"}, method = RequestMethod.GET)
    @ResponseBody
    public IotConfig getTenantIotConfig(
            @ApiParam(value = "A string value representing the IotConfig title.")
            @RequestParam String iotConfigTitle) throws ThingsboardException {
        try {
            TenantId tenantId = getCurrentUser().getTenantId();
            return checkNotNull(iotConfigService.findIotConfigByTenantIdAndTitle(tenantId, iotConfigTitle), "IotConfig with title [" + iotConfigTitle + "] is not found");
        } catch (Exception e) {
            throw handleException(e);
        }
    }

}
